<?php

/**
 * Vega Framework
 * 
 * @url http://vega.com.vn
 * @url http://clip.vn
 */

/**
 * Database access class that supports replication
 *
 */

abstract class Vega_Db_Abstract
{
    
    const MASTER = 'master';
    
    const SLAVE = 'slave';
    
    const FETCH_ASSOC = 1;
    
    const FETCH_NUM = 2;
    
    const FETCH_BOTH = 3;
    
    protected $_functions = array();

    protected $_connInfo = array();

    protected $_conns = array();

    protected $_currentResult = null;
    
    protected $_fetchStyle = 1;
    
    protected $_profiles = array();
    
    protected $_connMode = null;

    public function __construct($masterConfig, $slaveConfig = null)
    {
        $this->_connInfo['master'] = $masterConfig;

        if (null !== $slaveConfig)
        {
            $this->_connInfo['slave'] = $slaveConfig;
        }
    }

    /**
     * Makes a connection
     *
     * @param string $conn
     * @param bool $persistent
     * @return resource
     */
    protected function _connect($conn, $persistent = false)
    {
        if ($persistent)
        {
            $function = $this->_functions['pconnect'];
        }
        else
        {
            $function = $this->_functions['connect'];
        }
        
        $connection = $function($this->_connInfo[$conn]['host'], $this->_connInfo[$conn]['username'], $this->_connInfo[$conn]['password']);
        
        if (!$connection)
        {
            require_once 'Vega/Db/Exception.php';
            throw new Vega_Db_Exception('Could not connect to mysql ' . $conn . ' database');
        }
        $this->selectDb($this->_connInfo[$conn]['database'], $connection);
        
        return $connection;
    }
    
    /**
     * Select Db
     *
     * @param string $dbName
     * @param resource $connection
     */
    public function selectDb($dbName, $connection)
    {
        $this->_functions['select_db']($dbName, $connection);
    }
    
    /**
     * If the slave connection info is not set. Try to connect to the master.
     *
     * @param string $connName
     * @return Vega_Db_Abstract
     */
    public function connect($connName)
    {
        if (isset($this->_conns[$connName]) && (is_resource($this->_conns[$connName]) || is_object($this->_conns[$connName])))
        {
            return $this;
        }
        
        $persistent = (isset($this->_connInfo[$connName]['persistent'])) ? $this->_connInfo[$connName]['persistent'] : false;
        
        if ($connName == 'slave' && !isset($this->_connInfo[$connName]))
        {
            $connection = $this->_connect('master', $persistent);
            $this->_conns['master'] = $connection;
            $this->_conns['slave'] = &$this->_conns['master'];
        }
        else
        {
            $this->_conns[$connName] = $this->_connect($connName, $persistent);
        }
        $this->_query('SET NAMES utf8', $connName);
        return $this;
    }
    
    /**
     * Quote value if it is tring.
     *
     * @param mixed $value
     * @return string 
     */
    public function valueQuote($value)
    {
        if (is_string($value))
        {
            return "'" . $this->escape($value) . "'";
        }
        elseif (is_int($value))
        {
            return intval($value);
        }
        elseif (is_float($value))
        {
            return floatval($value);
        }
        elseif (is_array($value) && (isset($value['type']) && $value['type'] == 'function'))
        {
            return $this->escape($value);
        }
    }
    
    /**
     * Quote a field name
     *
     * @param string $field
     */
    abstract public function fieldQuote($field);
    
    public function setFetchStyle($style)
    {
        $this->_fetchStyle = $style;
    }
    
    

    /**
     * Does a SQL query.
     * This is stand behind all SQL operation funtions
     *
     * @param string $sql
     * @param string $conn
     * @return resource
     */
    protected function _query($sql, $conn)
    {
        $this->connect($conn);
        //mysql_query("SET NAMES utf8");
        
        $startTime = microtime(true);
        $result = $this->_functions['query']($sql, $this->_conns[$conn]);
        $endTime = microtime(true);
        
        $config = Vega_App::getInstance()->coreConfig;
        
		$webDebug = isset($config['webdebug']);
		if ($webDebug)
			$webDebug &= $config['webdebug']['enabled'] || isset($_REQUEST[$config['webdebug']['secretkey']]);
        if ($webDebug)
        {
        	Vega_Log_WebDebug::getDebugger()->addQueryInfo($sql, $endTime-$startTime);
        }
        
        if ($config['logging']['query']['enabled'] || isset($_GET['querylog']))
        {
            require_once 'Vega/Log/Writer.php';
            $content = chr(10).$sql.chr(10).'Time: '.($endTime-$startTime).' ms'.chr(10).'--------------------------------------------------------------';
            $log = new Vega_Log_Writer($config['logging']['dir'].DS.$config['logging']['query']['file']);
            $log->write($content);
        }
        
        if (false === $result)
        {
            require_once 'Vega/Db/Exception.php';
            throw new Vega_Db_Exception('SQL error: ' . $this->_functions['error']($this->_conns[$conn]) . ' in ' . $sql);
        }
        
        return $result;
    }
    
    /**
     * Does the insert query.
     * Returns last insert id, if any.
     *
     * @param string $sql
     * @return integer
     */
    protected function _insert($sql)
    {
        $result = $this->_query($sql, 'master');
        
        return $this->getLastInsertId();
    }
    
    /**
     * Makes multiple insert query
     *
     * @param string $table
     * @param array $data
     * @return mixed
     */
    public function multipleInsert($table, $data)
    {
        $sql = 'INSERT INTO ' . $table . ' (';

        $values = $keys = array();

        foreach ($data as $key => $value)
        {
            // multiple insert

            if ($keys == array())
            {
                $keys = array_keys($value);
            }

            foreach ($value as $k => $v)
            {
                $value[$k] = $this->valueQuote($v);
            }
            $values[] = implode(',', $value);
        }

        $sql .= implode(',', $keys) . ') VALUES (' . implode('), (', $values) . ')';
        
        return $this->_insert($sql);
    }

    /**
     * Makes single insert query
     *
     * @param string $table
     * @param array $data
     * @return mixed
     */
    public function insert($table, $data)
    {
        $sql = 'INSERT INTO ' . $table . ' (';
        $values = $keys = array();
        $keys = array_keys($data);

        foreach ($data as $key => $value)
        {
            $values[$key] = $this->valueQuote($value);
        }
        $sql .= implode(',', $keys) . ') VALUES (' . implode(', ', $values) . ')';
        return $this->_insert($sql);
    }

    /**
     * Makes update query
     *
     * @param string $table
     * @param array $data
     * @param string $where
     * @return boolean
     */
    public function update($table, $data, $where = '1 = 1')
    {
        $sql = 'UPDATE ' . $table . ' SET ';

        $segments = array();
        foreach ($data as $key => $value)
        {
            $segments[] = $key . ' = ' . $this->valueQuote($value);
        }
        $sql .= implode(', ', $segments) . ' WHERE ' . $where;
        return $this->_query($sql, 'master');
    }

    /**
     * Makes delete query
     *
     * @param string $table
     * @param string $where
     * @return boolean
     */
    public function delete($table, $where = '1 = 1')
    {
        $sql = 'DELETE FROM ' . $table . ' WHERE ' . $where;
        return $this->_query($sql, 'master');
    }
    
    /**
     * Makes custom query.
     * WARNING: This function is for read-only queries.
     *
     * @param string $sql
     * @param string $conn
     * @return mixed
     */
    public function query($sql, $conn = 'slave')
    {
    	if ($this->_connMode)
    		$conn = $this->_connMode;
    		
        $result = $this->_query($sql, $conn);
        
        $this->_currentResult = $result;
        return $result;
    }
    
    /**
     * Makes a custom write query
     * This is useful for developers who dont wanna use
     * update(), insert(), multipleInsert() or delete()
     *
     * @param string $sql
     * @return mixed
     */
    public function queryWrite($sql)
    {
        $result = $this->_query($sql, self::MASTER);
        
        if (!$result)
        {
            return false;
        }
        return $this->affectedRows();
    }

    /**
     * Returns affected rows of the lastest query.
     * Works only when the lastest query is a write query.
     *
     * @return integer
     */
    public function affectedRows()
    {
        return $this->_functions['affected_rows']($this->_conns[self::MASTER]);
    }
    
    
    /**
     * Returns current result
     *
     * @return resource
     */
    public function getCurrentResult()
    {
        return $this->_currentResult;
    }
    
    /**
     * Get the last auto-generated primary key
     *
     * @return integer
     */
    public function getLastInsertId()
    {
        return $this->_functions['insert_id']($this->_conns['master']);
    }
    
    /**
     * Fetchs a row in result set
     *
     * @param mixed $sql
     * @return array
     */
    public function fetchRow($sql = null)
    {
        if (is_resource($sql) || is_object($sql))
        {
            // do nothing. 
        }
        elseif ($sql === null)
        {
            $sql = &$this->_currentResult;
        }
        else
        {
            // if $sql is not current result and not a resource
            return false;
        }
        
        return $this->_functions['fetch_array']($sql, $this->_fetchStyle);
    }
    
    /**
     * Fetchs result set.
     * $sql can be a SQL string, a query result,
     * if it's null, uses the current result set.
     *
     * @param mixed $sql
     * @param string $conn
     * @return mixed
     */
    public function fetchRowset($sql = null, $conn = 'slave', $autoFree = false)
    {
        if (is_string($sql))
        {
            $sql = $this->query($sql, $conn);
        }
        elseif ($sql === null)
        {
            $sql = &$this->_currentResult;
        }
        elseif (!is_resource($sql) && !is_object($sql))
        {
            // if $sql is not a query string, not a current result and not a resource
            return false;
        }

        // use Rowset object
        /*if ($fetchRowsetObject)
        {
            require_once 'Vega/Db/Rowset.php';
            return new Vega_Db_Rowset($this, $sql);
        }*/

        $rows = array();
        while ($row = $this->_functions['fetch_array']($sql, $this->_fetchStyle))
        {
            $rows[] = $row;
        }

        if ($autoFree)
        {
            $this->free($sql);
        }
        
        return $rows;
    }
    
    /**
     * Escape sql string
     *
     * @param string $string
     * @return string
     */
    public function escape($string)
    {
        return $this->_functions['escape_string']($string);
    }    
    
    /**
     * Gets rows count of the result set
     *
     * @param resource $result
     * @return integer
     */
    public function numRows($result = null)
    {
        if (null == $result)
        {
            return $this->_functions['num_rows']($this->_currentResult);
        }
        return $this->_functions['num_rows']($result);
    }
    
    /**
     * Frees query result
     *
     * @param resource $result
     * @return boolean
     */
    public function free($result = null)
    {
        if (null == $result)
        {
            return $this->_functions['free_result']($this->_currentResult);
        }
        return $this->_functions['free_result']($result);
    }
    
    public function setMasterMode($bool) {
    	$this->_connMode = ($bool)?self::MASTER:null;    		
    }

    
    /**
     * Modifies the select query, add limit syntax
     *
     * @param string $sql
     * @param integer $limit
     * @param integer $offset
     */
    abstract public function limit($sql, $limit, $offset);
    
    /**
     * Begins a transaction
     *
     */
    abstract public function beginTransaction();
    
    /**
     * Rolls back a transaction
     *
     */
    abstract public function rollBack();
    
    /**
     * Commits
     *
     */
    abstract public function commit();
}

